package cn.jtfadmin.base.lang.common.utils;


import cn.jtfadmin.base.lang.common.domain.dto.AntdvTreeDataRDTO;
import cn.jtfadmin.base.lang.common.domain.dto.TreeRDTO;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 构建 属性结构的工具类
 *
 * @author jtf
 * @version 0.0.1
 * @see TreeRDTO
 * @since 0.0.1
 */
public class TreeUtils {




    /**
     * 构建树型对象
     *
     * @param resultType     结果类型，必须是标准javaBeans 非空
     * @param source         源数据，集合内容不能有空对象 非空
     * @param idProperty     id属性 非空
     * @param pidProperty    父id属性 非空
     * @param comparator     排序比较器 可空
     * @param ignoreProperty 复制到结果的忽略属性
     * @param <T>            泛型类型， {id:'xxx', pid:'xxx',xxx:'xxx'} 类型
     * @return 树对象
     */
    public static <T, V> List<TreeRDTO<T>> buildTreeRDTO(Class<T> resultType,
                                                      List<V> source,
                                                      String idProperty,
                                                      String pidProperty,
                                                      Comparator<V> comparator,
                                                      String... ignoreProperty) {
        Objects.requireNonNull(resultType, "构建树型对象，结果类型不能为空");
        Objects.requireNonNull(source, "构建树型对象，源数据不能为空");
        Objects.requireNonNull(idProperty, "构建树型对象，id属性不能为空");
        Objects.requireNonNull(pidProperty, "构建树型对象，父id属性不能为空");
        validLoop(source, idProperty, pidProperty);
        List<V> top = findTopParent(source, idProperty, pidProperty);
        return top.stream()
                .map(t -> transferToTreeRDTO(resultType,
                        t, source, idProperty, pidProperty, comparator, ignoreProperty))
                .collect(Collectors.toList());
    }

    /**
     * 获取顶级父
     *
     * @param source      源数据 集合内容不能有空对象 非空
     * @param idProperty  id属性 非空
     * @param pidProperty 父id属性 非空
     * @return 顶级父集合
     */
    public static <T> List<T> findTopParent(List<T> source,
                                             String idProperty,
                                             String pidProperty) {
        return source.stream()
                .filter(t -> Objects.isNull(findParent(source, t, idProperty, pidProperty)))
                .collect(Collectors.toList());
    }

    /**
     * 验证是否有循环嵌套
     *
     * @param source      源数据
     * @param idProperty  id 属性名称
     * @param pidProperty pid 属性名称
     * @throws IllegalArgumentException 当属性不能查询时,或循环嵌套
     */
    public static void validLoop(List<?> source,
                                 String idProperty,
                                 String pidProperty) throws IllegalArgumentException {
        for (Object o : source) {
            Set<Object> parents = new HashSet<>();
            Object parent = findParent(source, o, idProperty, pidProperty);
            while (Objects.nonNull(parent)) {
                if (parents.contains(parent)) {
                    throw new IllegalArgumentException("树对象，循环嵌套");
                }
                parents.add(parent);
                parent = findParent(source, parent, idProperty, pidProperty);
            }
        }
    }

    /**
     * 获取父对象
     *
     * @param source      源数据
     * @param child       child
     * @param idProperty  id属性
     * @param pidProperty 父id属性
     * @return 父对象， 可能为空
     */
    private static <T> T findParent(List<T> source, Object child,
                                     String idProperty, String pidProperty) {
        Object pid = findProperty(child, pidProperty);
        if (Objects.isNull(pid)) {
            return null;
        }
        for (T o : source) {
            Object id = findRequiredProperty(o, idProperty);
            if (Objects.equals(id, pid)) {
                return o;
            }
        }
        return null;

    }


    /**
     * 获取对象属相
     *
     * @param o              获取对象的属性
     * @param propertiesName 属性名称
     * @return 属性值
     * @throws IllegalArgumentException 当属性不可访问时
     */
    private static Object findProperty(Object o, String propertiesName) throws IllegalArgumentException {
        try {
            return PropertyUtils.getProperty(o, propertiesName);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }


    /**
     * 将对象转换为树对象， 其没有验证是否循环，如果循环会报错误栈溢出
     *
     * @param resultType     结果类型
     * @param target         转换目标数据
     * @param sources        元数据
     * @param idProperty     id 属性
     * @param pidProperty    pid属性
     * @param comparator     比较器
     * @param ignoreProperty 忽略属性
     * @param <T>
     * @return 对象
     * @see #validLoop(List, String, String)
     */
    public static <T, V> TreeRDTO<T> transferToTreeRDTO(Class<T> resultType, Object target, List<V> sources,
                                                     String idProperty, String pidProperty, Comparator<V> comparator,
                                                     String... ignoreProperty) {
        TreeRDTO<T> treeRDTO = new TreeRDTO<>();
        T result = null;
        try {
            result = resultType.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("实例class:" + resultType.getName() + " 不能初始化");
        }
        try {
            AkUtils.copyProperties(target, result, ignoreProperty);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
        treeRDTO.setData(result);

        List<V> children = sources.stream()
                .filter(t -> Objects.equals(target, findParent(sources, t, idProperty, pidProperty)))
                .collect(Collectors.toList());
        if (comparator != null) {
            Collections.sort(children, comparator);
        }
        List<TreeRDTO<T>> childrenTree = new ArrayList<>();
        for (Object child : children) {
            childrenTree.add(transferToTreeRDTO(resultType, child, sources, idProperty, pidProperty, comparator, ignoreProperty));
        }

        if (!childrenTree.isEmpty()) {
            treeRDTO.setChildren(childrenTree);
        }
        return treeRDTO;
    }


    /**
     * 将树型对象转换为antd对象
     *
     * @param target      目标对象 不可空
     * @param sources     元数据 不可空
     * @param idProperty  id 树型 不可空
     * @param pidProperty 上级id树型  不可空
     * @param comparator  比较器 可空
     * @param showFields  展示列 不可空
     * @param <T>
     * @return
     */
    public static <T> AntdvTreeDataRDTO transferToAntdvTreeRDTO(T target, List<T> sources,
                                                                String idProperty, String pidProperty, Comparator<T> comparator,
                                                                String... showFields) {
        List<String> showFiledsTextList = new ArrayList<>();
        for (String showField : showFields) {
            String property = null;
            try {
                property = BeanUtils.getProperty(target, showField);
            } catch (Exception e) {
                throw new IllegalArgumentException(e);
            }
            showFiledsTextList.add(property);
        }
        String id = null;
        try {
            id = BeanUtils.getProperty(target, idProperty);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }


        List<T> children = sources.stream()
                .filter(t -> Objects.equals(target, findParent( sources, t, idProperty, pidProperty)))
                .collect(Collectors.toList());

        Collections.sort(children, comparator);
        List<AntdvTreeDataRDTO> childrenTree = new ArrayList<>();
        for (T child : children) {
            childrenTree.add(transferToAntdvTreeRDTO(child, sources, idProperty, pidProperty, comparator, showFields));
        }

        if (!childrenTree.isEmpty()) {
            return new AntdvTreeDataRDTO(StringUtils.collectionToDelimitedString(showFiledsTextList, "--"), id, childrenTree);
        }
        return new AntdvTreeDataRDTO(StringUtils.collectionToDelimitedString(showFiledsTextList, "--"), id, null);
    }

    /**
     * 获取必须对象属相
     *
     * @param o              获取对象的属性
     * @param propertiesName 属性名称
     * @return 属性值
     * @throws IllegalArgumentException 当属性不可访问时或属性为空
     */
    private static Object findRequiredProperty(Object o, String propertiesName) throws IllegalArgumentException {
        Object object = findProperty(o, propertiesName);
        if (Objects.isNull(object)) {
            throw new IllegalArgumentException("必须的属性为空");
        }
        return object;
    }


}
